#include "../Headers/tFile.hpp"
#include "../Headers/tTime.hpp"
#include "../Headers/tLogger.hpp"

#include <Windows.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <AtlBase.h>
#include <direct.h>
#include <tchar.h>
#include <io.h>
#include <sys/stat.h>

#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#include <experimental/filesystem>

#define PLATFORM_WIN

using namespace std;

std::string TWrite2Txt::filenameGlobalTXT = std::string("Filename not assigned");
bool TWrite2Txt::flagFilenameGlobalSet = false;


LPCWSTR TFile_string2LPCWSTR(std::string strIn, std::wstring* wstrBuffer = nullptr) {
	const static int bufferNum = 64;
	static std::wstring defaultWstrBuffers[bufferNum];
	static int bufCounter = 0;
	std::wstring* thisBuffer = defaultWstrBuffers + bufCounter;
	bufCounter++;
	if (bufCounter == bufferNum) {
		bufCounter = 0;
	}
	if (wstrBuffer == nullptr)
		wstrBuffer = thisBuffer;
	*wstrBuffer = std::wstring(strIn.begin(), strIn.end());
	LPCWSTR sw = (*wstrBuffer).c_str();
	return sw;
}

string TFile_Wchar_tToString(wchar_t* wchar)
{
	wchar_t* wText = wchar;
	DWORD dwNum = WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, NULL, 0, NULL, FALSE);
	char* psText;
	psText = new char[dwNum];
	WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, psText, dwNum, NULL, FALSE);
	std::string strOut = psText;
	delete[]psText;
	return strOut;
}

string TFile_int2str(int val) {
	string s;
	stringstream ss(s);
	ss << val;
	return ss.str();
}

wstring TFile_int2wstr(int val) {
	wstring s;
	wstringstream ss(s);
	ss << val;
	return ss.str();
}

class TSortForTFile {
public:
	template<class Type>
	static void sortAutoIndex(Type* dataIn, int dataNum, int* indexes) {
		if (dataNum <= 0)
			return;
		for (int dataIdx = 0; dataIdx < dataNum; dataIdx++) {
			indexes[dataIdx] = dataIdx;
		}
		sortCore(indexes, dataIn, 0, dataNum - 1);
	}

private:
	template<class Type>
	static void sortCore(int* indexes, Type* dataIn, int low, int high) {
		if (low < high) {
			int result = partition(indexes, dataIn, low, high);
			sortCore(indexes, dataIn, low, result - 1);
			sortCore(indexes, dataIn, result + 1, high);
		}
	}

	template<class Type>
	static int partition(int* indexes, Type* sortArray, int low, int high) {
		Type key = sortArray[low];
		int currentIdx = indexes[low];
		while (low < high) {
			while (low < high && sortArray[high] >= key)
				high--;
			sortArray[low] = sortArray[high];
			indexes[low] = indexes[high];

			while (low < high && sortArray[low] <= key)
				low++;
			sortArray[high] = sortArray[low];
			indexes[high] = indexes[low];
		}
		sortArray[low] = key;
		indexes[low] = currentIdx;
		return low;
	};
};

int TFile::isFileOrDir(std::wstring filenameIn) {
#ifdef PLATFORM_WIN
	if (GetFileAttributes(filenameIn.c_str()) == INVALID_FILE_ATTRIBUTES)
		return -1;
	if (PathIsDirectory(filenameIn.c_str()))
		return 1;
	else
		return 0;
#else
	return 0;
#endif
}

int TFile::countFileDirNumberInDir(std::wstring dirnameIn) {
#ifdef PLATFORM_WIN
	if (isFileOrDir(dirnameIn) <= 0) {
		return -1;
	}

	dirnameIn = TFile::addSlashIfNeeded(dirnameIn);
	dirnameIn += L"*";
	vector<wstring> filenames;
	TFile::listFiles(dirnameIn, filenames);
	return filenames.size();
#else
	return 0;
#endif
}

void TFile::initializeDirForSWSIMobileDev(std::string imageFolder) {
#ifdef PLATFORM_WIN
	std::string debugFolderName("debug");
	std::string correctionFolderName("correction");
	char debugFolder[1024];
	char correctionFolder[1024];

	if ((imageFolder[imageFolder.length() - 1] != '/') && (imageFolder[imageFolder.length() - 1] != '\\'))
		imageFolder.append("/");
	for (int idx = 0; idx < imageFolder.size(); idx++) {
		debugFolder[idx] = imageFolder[idx];
		correctionFolder[idx] = imageFolder[idx];
	}

	_mkdir(debugFolder);
	_mkdir(correctionFolder);
#endif
}

void TFile::sortFileNamesWithNum(std::vector<std::wstring>& filesIn, std::vector<std::wstring>* filesSortedOut) {
	if (filesIn.size() == 0) {
		return;
	}
	vector<int> fileCodes(filesIn.size());
	vector<int> fileIdxInOriginalSeq(filesIn.size());
	std::vector<std::wstring> filesSorted;
	filesSorted.resize(filesIn.size());
	for (int fileIdx = 0; fileIdx < filesIn.size(); fileIdx++) {
		wstring filenameCore = getFilenameNoDirOnly(filesIn[fileIdx]);
		int charPosStart, charPosEnd;
		for (charPosStart = 0; charPosStart < filenameCore.length(); charPosStart++) {
			if (filenameCore[charPosStart] >= '0' && filenameCore[charPosStart] <= '9') {
				break;
			}
		}
		for (charPosEnd = charPosStart + 1; charPosEnd < filenameCore.length(); charPosEnd++) {
			if (filenameCore[charPosEnd] < '0' || filenameCore[charPosEnd] > '9') {
				break;
			}
		}
		wstring fileCodeStr = filenameCore.substr(charPosStart, charPosEnd - charPosStart);
		fileCodes[fileIdx] = std::stoi(fileCodeStr);
		if ((charPosStart > 1) && (filenameCore[charPosStart - 1] == '_') && (filenameCore[charPosStart - 2] == '_'))
			fileCodes[fileIdx] = -fileCodes[fileIdx];
	}
	TSortForTFile::sortAutoIndex(&fileCodes[0], (int)fileCodes.size(), &fileIdxInOriginalSeq[0]);
	for (int fileIdx = 0; fileIdx < filesIn.size(); fileIdx++) {
		filesSorted[fileIdx] = filesIn[fileIdxInOriginalSeq[fileIdx]];
	}
	if (filesSortedOut == nullptr) {
		filesIn = filesSorted;
	}
	else {
		*filesSortedOut = filesSorted;
	}
}

std::wstring TFile::searchForFile(std::wstring filenameFormat) {
#ifdef PLATFORM_WIN
	TCHAR root[1024];
	for (int idx = 0; idx < filenameFormat.length(); idx++) {
		root[idx] = filenameFormat[idx];
	}
	root[filenameFormat.length()] = '\0';

	_tfinddata64_t c_file;
	intptr_t hFile;
	hFile = _tfindfirst64(root, &c_file);
	if (hFile == -1) {
		return L"";
	}
	else {
		wstring dirname = getDirNoFilenameOnly(filenameFormat);
		return dirname + c_file.name;
	}
#else
	return "";
#endif
}

void TFile::listFiles(std::wstring folderName, std::wstring filenamePattern, std::vector<std::wstring>& files) {
#ifdef PLATFORM_WIN
	if (folderName.length() <= 1) {
		return;
	}
	const int MIN_FILENAME_LEN = (int)(filenamePattern.size() - 1);
	_tfinddata64_t c_file;
	TCHAR root[1024];

	filenamePattern = addSlashIfNeeded(folderName) + filenamePattern;

	for (int idx = 0; idx < filenamePattern.length(); idx++) {
		root[idx] = filenamePattern[idx];
	}
	root[filenamePattern.length()] = '\0';

	intptr_t hFile;
	hFile = _tfindfirst64(root, &c_file);
	if (hFile == -1)
		return;
	//cout << "can't open this filefolder" << endl;
	//search all files recursively.
	do
	{
		std::wstring szDest = c_file.name;
		if (szDest.compare(L".") == 0 || szDest.compare(L"..") == 0) {
			continue;
		}
		if (szDest.size() > MIN_FILENAME_LEN) {
			//if (szDest[0] != '_')
			files.push_back(folderName + szDest);
		}
	} while (_tfindnext64(hFile, &c_file) == 0);
	_findclose(hFile);
#endif
}

std::vector<std::wstring> TFile::listFiles(std::wstring filenamePattern) {
	vector<std::wstring> files;
	listFiles(filenamePattern, files);
	return files;
}

void TFile::listFiles(std::wstring filenamePattern, std::vector<std::wstring>& files) {
#ifdef PLATFORM_WIN
	//{
	//	TCHAR root[1024] = L"C:/testData/?????_?????.jpg";
	//	_tfinddata64_t c_file;
	//	intptr_t hFile = _tfindfirst64(root, &c_file);
	//	do
	//	{
	//		wcout << c_file.name << endl;
	//	} while (_tfindnext64(hFile, &c_file) == 0);
	//	_findclose(hFile);
	//}


	const int MIN_FILENAME_LEN = (int)(filenamePattern.size() - 1);
	TCHAR root[1024];

	for (int idx = 0; idx < filenamePattern.length(); idx++) {
		root[idx] = filenamePattern[idx];
	}
	root[filenamePattern.length()] = '\0';

	_tfinddata64_t c_file;
	intptr_t hFile = _tfindfirst64(root, &c_file);
	if (hFile == -1)
		return;
	//cout << "can't open this filefolder" << endl;
	//search all files recursively.
	wstring folderName = getDirNoFilenameOnly(filenamePattern);
	do
	{
		std::wstring szDest = c_file.name;
		files.push_back(wstring(folderName).append(szDest));
	} while (_tfindnext64(hFile, &c_file) == 0);
	_findclose(hFile);
#endif
}

void TFile::listImageFiles(std::wstring imageFolder, std::vector<std::wstring>& files, bool sorted) {
#ifdef PLATFORM_WIN
	//std::vector<std::string> filesUnsorted;

	listFiles(imageFolder, std::wstring(L"*.bmp"), files);
	if (files.size() == 0) {
		listFiles(imageFolder, std::wstring(L"*.jpg"), files);
		if (files.size() == 0) {
			listFiles(imageFolder, std::wstring(L"*.jpeg"), files);
			if (files.size() == 0) {
				listFiles(imageFolder, std::wstring(L"*.png"), files);
				if (files.size() == 0) {
					TLogger::writeWarnning("No image files found!");
				}
			}
		}
	}
	if (files.size() > 0) {
		TFile::sortFileNamesWithNum(files);
	}
#endif
}

void TFile::deleteFile(std::wstring filename) {
	DeleteFile(filename.c_str());
}

void TFile::deleteFiles(std::wstring imageFolder, std::wstring filenamePattern) {
	if (imageFolder.length() <= 1) {
		if (isFileOrDir(filenamePattern) == 0) {
			DeleteFile(filenamePattern.c_str());
		}
	}
	else {
		vector<wstring> filenames;
		listFiles(imageFolder, filenamePattern, filenames);
		for (wstring filename : filenames) {
			if (isFileOrDir(filename) == 0) {
				DeleteFile(filename.c_str());
			}
		}
	}
}

void TFile::copyFile(std::wstring src, std::wstring dst, bool flagSkipIfSameSize) {
	size_t sizeSrc, sizeDst;
	{
		struct _stat64 statbuf;
		_wstat64(src.c_str(), &statbuf);
		sizeSrc = statbuf.st_size;
	}
	{
		struct _stat64 statbuf;
		_wstat64(dst.c_str(), &statbuf);
		sizeDst = statbuf.st_size;
	}

	if (flagSkipIfSameSize) {
		if (sizeSrc == sizeDst) {
			return;
		}
	}
	CopyFile(src.c_str(), dst.c_str(), FALSE);
}

void TFile::renameFile(std::wstring oldName, std::wstring newName) {
	_wrename(oldName.c_str(), newName.c_str());
}

void TFile::makeDirTreeIfNeeded(std::wstring fullPath) {
#ifdef PLATFORM_WIN
	vector<size_t> cutPoses;
	size_t cutPos = 0;
	while (true) {
		cutPos = findNextSlash(fullPath, cutPos);
		if (cutPos == string::npos) {
			break;
		}
		else {
			cutPoses.push_back(cutPos++);
		}
	}
	for (size_t cutPos : cutPoses) {
		wstring dirNode = fullPath.substr(0, cutPos);
		if (TFile::isFileOrDir(dirNode) != 1) {
			_wmkdir(dirNode.c_str());
		}
	}
#endif
}

void TFile::clearDir(std::wstring folderName, std::wstring filenamePattern) {
#ifdef PLATFORM_WIN
	folderName = addSlashIfNeeded(folderName);
	int pos0 = folderName.find_first_of('\\', 3);
	int pos1 = folderName.find_first_of('/', 3);
	int curSlashPos = max((int)folderName.find_first_of('\\', 3), (int)folderName.find_first_of('/', 3));
	while (curSlashPos > 0) {
		wstring newDirLv = folderName.substr(0, curSlashPos);
		if (isFileOrDir(newDirLv) < 0) {
			_wmkdir(&newDirLv[0]);
		}
		curSlashPos = max((int)folderName.find_first_of('\\', curSlashPos + 1), (int)folderName.find_first_of('/', curSlashPos + 1));
	}
	vector<wstring> filenames;
	//listFilesOrDirs(folderName, filenames);
	listFiles(folderName, filenamePattern, filenames);
	for (wstring& filename : filenames) {
		//DeleteFile(TFile_string2LPCWSTR(filename));
		//std::filesystem::remove_all(filename);
		if (TFile::isFileOrDir(filename) >= 0) {
			try {
				std::experimental::filesystem::remove_all(filename);
			}
			catch (exception e) {
				TLogger::writeLine(e.what());
			}
		}
	}
#endif
}

std::string TFile::resolveShortcutFile(std::string mayBeShortCutFileName) {
#ifdef PLATFORM_WIN
	// https://stackoverflow.com/questions/22986845/windows-read-the-target-of-shortcut-file-in-c
	//HRESULT hres;
	//IShellLink* psl;
	//WCHAR szGotPath[MAX_PATH];
	//WCHAR szDescription[MAX_PATH];
	//WIN32_FIND_DATA wfd;

	//// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
	//// has already been called. 
	//hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
	//if (SUCCEEDED(hres))
	//{
	//	IPersistFile* ppf;

	//	// Get a pointer to the IPersistFile interface. 
	//	hres = psl->QueryInterface(IID_IPersistFile, (void**)&ppf);

	//	if (SUCCEEDED(hres))
	//	{
	//		WCHAR wsz[MAX_PATH];

	//		// Ensure that the string is Unicode. 
	//		
	//		MultiByteToWideChar(CP_ACP, 0, lpszLinkFile, -1, wsz, MAX_PATH);

	//		// Add code here to check return value from MultiByteWideChar 
	//		// for success.

	//		// Load the shortcut. 
	//		hres = ppf->Load(wsz, STGM_READ);

	//		if (SUCCEEDED(hres))
	//		{
	//			// Resolve the link. 
	//			hres = psl->Resolve(hwnd, 0);

	//			if (SUCCEEDED(hres))
	//			{
	//				// Get the path to the link target. 
	//				hres = psl->GetPath(szGotPath, MAX_PATH, (WIN32_FIND_DATA*)&wfd, SLGP_SHORTPATH);

	//				if (SUCCEEDED(hres))
	//				{
	//					// Get the description of the target. 
	//					hres = psl->GetDescription(szDescription, MAX_PATH);

	//					if (SUCCEEDED(hres))
	//					{
	//						hres = StringCbCopy(lpszPath, iPathBufferSize, szGotPath);
	//						if (SUCCEEDED(hres))
	//						{
	//							// Handle success
	//						}
	//						else
	//						{
	//							// Handle the error
	//						}
	//					}
	//				}
	//			}
	//		}

	//		// Release the pointer to the IPersistFile interface. 
	//		ppf->Release();
	//	}

	//	// Release the pointer to the IShellLink interface. 
	//	psl->Release();
	//}
	//return hres;
	return "";
#else
	return "";
#endif
}

std::string TFile::renameFile(std::string filenameWithFullDir, std::string outputDirWithSlash, std::string outputSurfix, bool includeParentDirInName) {
	int lastSlashPos;
	int secondLastSlashPos;
	int dotPos;

	for (dotPos = (int)filenameWithFullDir.length() - 1; dotPos >= 0; dotPos--) {
		if (filenameWithFullDir[dotPos] == '.')
			break;
	}
	for (lastSlashPos = (int)filenameWithFullDir.length() - 1; lastSlashPos >= 0; lastSlashPos--) {
		if (filenameWithFullDir[lastSlashPos] == '/' || filenameWithFullDir[lastSlashPos] == '\\')
			break;
	}
	for (secondLastSlashPos = lastSlashPos - 1; secondLastSlashPos >= 0; secondLastSlashPos--) {
		if (filenameWithFullDir[secondLastSlashPos] == '/' || filenameWithFullDir[secondLastSlashPos] == '\\')
			break;
	}
	string outputFilename;
	if (dotPos > 0) {
		outputFilename = filenameWithFullDir.substr(lastSlashPos + 1, dotPos - lastSlashPos - 1);
		outputFilename += "_" + filenameWithFullDir.substr(dotPos + 1, filenameWithFullDir.length() - dotPos - 1) + outputSurfix;
	}
	else {
		outputFilename = filenameWithFullDir.substr(lastSlashPos + 1, filenameWithFullDir.length() - lastSlashPos - 1) + outputSurfix;
	}
	if (includeParentDirInName && secondLastSlashPos > 0) {
		string parentDirNameCore = filenameWithFullDir.substr(secondLastSlashPos + 1, lastSlashPos - secondLastSlashPos - 1);
		outputFilename = replaceHiphenWithUnderscore(parentDirNameCore) + "_" + outputFilename;
	}
	outputFilename = outputDirWithSlash + outputFilename;
	return outputFilename;
}

std::wstring TFile::addSlashIfNeeded(std::wstring dirName) {
	if (dirName.length() == 0) {
		return dirName;
	}
	if (dirName[dirName.size() - 1] == '/' || dirName[dirName.size() - 1] == '\\') {
		return dirName;
	}
	else {
		return dirName + L"/";
	}
}


std::wstring TFile::getFilenameNoDirOnly(std::wstring filenameFullIn, std::wstring& surfixOut) {
	int slashPos = -1;
	int dotPos = -1;
	for (int offset = (int)filenameFullIn.length() - 1; offset >= 0; offset--) {
		if (dotPos < 0 && filenameFullIn[offset] == '.') {
			dotPos = offset;
		}
		if (filenameFullIn[offset] == '/' || filenameFullIn[offset] == '\\') {
			slashPos = offset;
			break;
		}
	}
	wstring filenameNoDir;
	if (dotPos > 0 && slashPos > 0) {
		surfixOut = filenameFullIn.substr(dotPos + 1, filenameFullIn.length() - dotPos);
		filenameNoDir = filenameFullIn.substr(slashPos + 1, filenameFullIn.length() - surfixOut.length() - 1 - slashPos - 1);
	}
	if (dotPos > 0 && slashPos < 0) {
		// not tested?
		surfixOut = filenameFullIn.substr(dotPos + 1, filenameFullIn.length() - dotPos);
		filenameNoDir = filenameFullIn.substr(slashPos + 1, filenameFullIn.length() - surfixOut.length() - 1);
	}
	if (dotPos < 0 && slashPos > 0) {
		// not tested
	}
	if (dotPos < 0 && slashPos < 0) {
		// not tested
	}
	return filenameNoDir;
}

std::wstring TFile::getFilenameNoDirOnly(std::wstring filenameFullIn) {
	int lastSlashPos = -1;
	int slashPos = -1;
	int dotPos = -1;

	while (filenameFullIn.back() == L'/' || filenameFullIn.back() == L'\\') {
		filenameFullIn.pop_back();
	}

	for (int offset = (int)filenameFullIn.length() - 1; offset >= 0; offset--) {
		if (filenameFullIn[offset] == L'/' || filenameFullIn[offset] == L'\\') {
			lastSlashPos = slashPos;
			slashPos = offset;
			break;
		}
	}
	for (int offset = (int)filenameFullIn.length() - 1; offset >= slashPos; offset--) {
		if (dotPos < 0 && filenameFullIn[offset] == L'.') {
			dotPos = offset;
			break;
		}
	}

	wstring filenameNoDir;
	if (dotPos > 0 && slashPos > 0) {
		filenameNoDir = filenameFullIn.substr(slashPos + 1, filenameFullIn.length() - slashPos - 1);
	}
	if (dotPos > 0 && slashPos < 0) {
		// not tested?
		filenameNoDir = filenameFullIn.substr(slashPos + 1, filenameFullIn.length());
	}
	if (dotPos < 0 && slashPos > 0) {
		// not tested
		filenameNoDir = filenameFullIn.substr(slashPos + 1);
	}
	if (dotPos < 0 && slashPos < 0) {
		// not tested
	}
	return filenameNoDir;
}

std::wstring TFile::getDirNoFilenameOnly(std::wstring filenameFullIn) {
	int lastSlashPos0 = filenameFullIn.find_last_of('/');
	int lastSlashPos1 = filenameFullIn.find_last_of('\\');
	if (lastSlashPos0 == string::npos) {
		lastSlashPos0 = 0;
	}
	if (lastSlashPos1 == string::npos) {
		lastSlashPos1 = 0;
	}
	return filenameFullIn.substr(0, max(lastSlashPos0, lastSlashPos1) + 1);
}

std::vector<std::wstring> TFile::splitFilenameWithDirAndSurfix(std::wstring filenameFullIn) {
	std::vector<std::wstring> filenameAndSurfix;
	size_t dotPos = filenameFullIn.find_last_of('.');
	if (dotPos != wstring::npos) {
		filenameAndSurfix.push_back(filenameFullIn.substr(0, dotPos));
		filenameAndSurfix.push_back(filenameFullIn.substr(dotPos));
	}
	return filenameAndSurfix;
}

std::string TFile::getBottomDirName(std::string filenameFullIn) {
	size_t lastSlash0 = filenameFullIn.find_last_of('/');
	size_t lastSlash1 = filenameFullIn.find_last_of('\\');
	size_t lastSlashPos = min(lastSlash0, lastSlash1);
	string name = "";
	if (lastSlashPos < filenameFullIn.length() - 1) {
		name = filenameFullIn.substr(lastSlashPos + 1);
	}
	else {
		size_t secLastSlash0 = filenameFullIn.find_last_of('/', lastSlashPos - 1);
		size_t secLastSlash1 = filenameFullIn.find_last_of('\\', lastSlashPos - 1);
		size_t secLastSlashPos = min(secLastSlash0, secLastSlash1);
		name = filenameFullIn.substr(secLastSlashPos + 1, lastSlashPos - secLastSlashPos - 1);
	}
	if (name.back() == '\\' || name.back() == '/') {
		name.pop_back();
	}
	return name;
}

wstring TFile::makeFilenameByAppend(std::wstring refFilenameIn, std::wstring appendStr) {
	auto filenameParts = splitFilenameWithDirAndSurfix(refFilenameIn);
	if (filenameParts.size() > 0) {
		return filenameParts[0] + appendStr + filenameParts[1];
	}
	else {
		return refFilenameIn + appendStr;
	}
}

std::wstring TFile::getExeDir() {
	wchar_t path[MAX_PATH];

	if (GetModuleFileName(0, path, sizeof path))
	{
		wstring dir = TFile::getDirNoFilenameOnly(path);
		return dir;
	}
	return L"";
}

std::wstring TFile::getCodeDir() {
	const int bufferSize = 1024;
	wchar_t buffer[bufferSize];
	GetCurrentDirectory(bufferSize, buffer);
	return buffer;
}

std::string TFile::replaceHiphenWithUnderscore(std::string stringIn) {
	for (int idx = 0; idx < stringIn.length(); idx++)
		if (stringIn[idx] == '-')
			stringIn[idx] = '_';
	return stringIn;
}

std::string TFile::replaceInvalidCharForFilenameWithUnderscore(std::string stringIn) {
	for (int idx = 0; idx < stringIn.length(); idx++) {
		if ((stringIn[idx] == '\\') || (stringIn[idx] == '/') || (stringIn[idx] == ':') || (stringIn[idx] == '*')
			|| (stringIn[idx] == '?') || (stringIn[idx] == '\"') || (stringIn[idx] == '<') || (stringIn[idx] == '>') || (stringIn[idx] == '|')) {
			stringIn[idx] = '_';
		}
	}
	return stringIn;
}

bool TFile::isFilenameJPEG(std::string filenameIn) {
	int fileFormatPos = (int)filenameIn.find_last_of('.') + 1;
	if (fileFormatPos < 0)
		return false;
	if (filenameIn.size() - fileFormatPos == 3) {
		if ((filenameIn.substr(fileFormatPos, 3) == "jpg") || (filenameIn.substr(fileFormatPos, 3) == "JPG"))
			return true;
		else
			return false;
	}
	else 	if (filenameIn.size() - fileFormatPos == 4) {
		if ((filenameIn.substr(fileFormatPos, 4) == "JPEG") || (filenameIn.substr(fileFormatPos, 3) == "jpeg"))
			return true;
		else
			return false;
	}
	else return false;
}

static void tryRename(wstring fromFilename, wstring toFilename) {
	if (TFile::isFileOrDir(toFilename) == 0) {
		DeleteFile(toFilename.c_str());
	}
	_wrename(fromFilename.c_str(), toFilename.c_str());
}

static bool tryCopy(wstring fromFilename, wstring toFilename) {
#ifdef PLATFORM_WIN
	return CopyFile(fromFilename.c_str(), toFilename.c_str(), FALSE);
#endif
}

std::wstring TFile::backupFile(std::wstring filenameIn, int keepBUNum, bool refreshImg) {
	if (isFileOrDir(filenameIn) == 0) {
		vector<wstring> filenameAndSurfix = splitFilenameWithDirAndSurfix(filenameIn);
		for (int buIdx = keepBUNum - 1; buIdx > 0; buIdx--) {
			wstring fromFilename = filenameAndSurfix[0] + L"_bu" + TFile_int2wstr(buIdx - 1) + filenameAndSurfix[1];
			wstring toFilename = filenameAndSurfix[0] + L"_bu" + TFile_int2wstr(buIdx) + filenameAndSurfix[1];
			tryRename(fromFilename, toFilename);
			//return toFilename;
		}
		if (keepBUNum > 0) {
			wstring fromFilename = filenameIn;
			wstring toFilename = filenameAndSurfix[0] + L"_bu" + TFile_int2wstr(0) + filenameAndSurfix[1];
			bool ret = false;
			if (refreshImg) {
				ret = tryCopy(fromFilename, toFilename);
			}
			else {
				tryRename(fromFilename, toFilename);
			}
			return toFilename;
		}
	}
	return L"";
}

size_t TFile::findNextSlash(std::wstring& strIn, size_t offset) {
	size_t bwSlashPos = strIn.find('/', offset);
	size_t fwSlashPos = strIn.find('\\', offset);
	if (bwSlashPos == string::npos) {
		return fwSlashPos;
	}
	else {
		if (fwSlashPos == string::npos) {
			return bwSlashPos;
		}
		else {
			return min(fwSlashPos, bwSlashPos);
		}
	}
}

std::vector<uint8_t> TWrite2Bin::mixTextAndBin(std::string& text, std::vector<uint8_t>& bin) {
	uint64_t txtLen = text.size();
	uint64_t binLen = bin.size();

	std::vector<uint8_t> ret;
	// add 2 uint_64: length of xml and length of binary
	ret.resize(txtLen + binLen + sizeof(txtLen) + sizeof(binLen));
	memcpy(&ret[0], &text[0], text.size());
	if (binLen > 0) {
		memcpy(&ret[txtLen], &bin[0], bin.size());
	}
	memcpy(&ret[txtLen + binLen], &txtLen, sizeof(txtLen));
	memcpy(&ret[txtLen + binLen + sizeof(txtLen)], &binLen, sizeof(binLen));
	return ret;
}

void TWrite2Bin::mixTextAndBin(std::string& text, std::vector<uint8_t>& bin, std::wstring filename) {
	std::vector<uint8_t> dat = mixTextAndBin(text, bin);
	writeByteStream(filename, &dat[0], dat.size());
}

void TWrite2Bin::combineArrays(std::vector<uint8_t>& array0Out,
	std::vector<uint8_t>* array1,
	std::vector<uint8_t>* array2,
	std::vector<uint8_t>* array3,
	std::vector<uint8_t>* array4) {
	int totalSize = array0Out.size();
	if (array1 != nullptr) {
		totalSize += array1->size();
	}
	if (array2 != nullptr) {
		totalSize += array2->size();
	}
	if (array3 != nullptr) {
		totalSize += array3->size();
	}
	if (array4 != nullptr) {
		totalSize += array4->size();
	}

	int sizeSum = array0Out.size();
	array0Out.resize(totalSize);

	if (array1 != nullptr && array1->size() > 0) {
		memcpy(&array0Out[sizeSum], array1->data(), array1->size());
		sizeSum += array1->size();
	}
	if (array2 != nullptr && array2->size() > 0) {
		memcpy(&array0Out[sizeSum], array2->data(), array2->size());
		sizeSum += array2->size();
	}
	if (array3 != nullptr && array3->size() > 0) {
		memcpy(&array0Out[sizeSum], array3->data(), array3->size());
		sizeSum += array3->size();
	}
	if (array4 != nullptr && array4->size() > 0) {
		memcpy(&array0Out[sizeSum], array4->data(), array4->size());
		sizeSum += array4->size();
	}
}

void TReadFromBin::splitTextAndBin(std::vector<uint8_t>& hybridTxtBin, std::string& textOut, uint8_t** dataPtr, size_t* dataLen) {
	splitTextAndBin(hybridTxtBin.data(), hybridTxtBin.size(), textOut, dataPtr, dataLen);
}

void TReadFromBin::splitTextAndBin(void* hybridTxtBin, size_t len, std::string& textOut, uint8_t** dataPtr, size_t* dataLen) {
	uint64_t txtLen = 0;
	uint64_t binLen = 0;
	uint8_t* srcRef = (uint8_t*)hybridTxtBin;

	memcpy(&txtLen, srcRef + len - sizeof(txtLen) - sizeof(binLen), sizeof(txtLen));
	memcpy(&binLen, srcRef + len - sizeof(binLen), sizeof(binLen));
	textOut.resize(txtLen);
	memcpy(&textOut[0], srcRef, txtLen);

	*dataPtr = srcRef + txtLen;
	*dataLen = binLen;
}

bool TWrite2Bin::writeByteStream(std::wstring filename, char* byteStream, size_t streamLen) {
	bool ret = true;
	ofstream myfile;
	myfile.open(filename, ios::out | ios::binary);
	ret &= !myfile.bad();
	myfile.write(byteStream, streamLen);
	ret &= !myfile.bad();
	myfile.close();
	return ret;
}

bool TWrite2Bin::writeByteStream(std::wstring filename, uint8_t* byteStream, size_t streamLen) {
	return writeByteStream(filename, (char*)byteStream, streamLen);
}

bool TWrite2Bin::writeByteStream(std::wstring filename, void* byteStream, size_t streamLen) {
	return writeByteStream(filename, (char*)byteStream, streamLen);
}

void TWrite2Bin::writeDb(ofstream& fileOut, float data) {
	fileOut.write(reinterpret_cast<char*>(&data), sizeof(data));
}

void TWrite2Bin::writeBool(ofstream& fileOut, bool data) {
	float dataOut;
	if (data)
		dataOut = 1;
	else
		dataOut = 0;
	writeDb(fileOut, dataOut);
}

void TWrite2Bin::writeDbArray2Bin(vector<float> data, std::string filename) {
	writeArray2Bin<float>(&data[0], data.size(), filename);
}

void TWrite2Bin::writeDbArray2Bin(float* data, int length, std::string filename) {
	writeArray2Bin<float>(data, length, filename);
}

void TWrite2Bin::writeDbArrayArrayEqualLen2Bin(std::vector<vector<float>>& data, std::string filename) {
	ofstream myfile;
	myfile.open(filename, ios::out | ios::binary);
	int arrayLen = (int)data.size();
	int eleLen = (int)data[0].size();
	writeDb(myfile, float(arrayLen));
	writeDb(myfile, float(eleLen));
	for (int idx0 = 0; idx0 < arrayLen; idx0++) {
		for (int idx1 = 0; idx1 < eleLen; idx1++) {
			writeDb(myfile, data[idx0][idx1]);
		}
	}
	myfile.close();
}

void TWrite2Bin::writeInt(std::ofstream& fileOut, int data) {
	fileOut.write(reinterpret_cast<char*>(&data), sizeof(data));
}

void TWrite2Bin::writeIntArray2Bin(int* data, int length, std::string filename) {
	writeArray2Bin<int>(data, length, filename);
}

void TWrite2Bin::writeBoolArray2Bin(bool* data, int width, int height, std::string filename) {
	write2DArray2Bin<bool>(data, width, height, filename);
}

std::fstream* getStreamNative(void* stream) {
	return (std::fstream*)stream;
}

TFileStreamer::TFileStreamer() {
	_stream = new fstream();

	flagStreamOpened = false;
}

TFileStreamer::TFileStreamer(std::string filename, bool trueForWriteFalseForRead) {
	_stream = new fstream();

	openFile(filename, trueForWriteFalseForRead);
}

TFileStreamer::~TFileStreamer() {
	closeFile();

	delete _stream;
}

void TFileStreamer::openFile(std::string filename, bool trueForWriteFalseForRead) {
#ifdef PLATFORM_WIN
	if (flagStreamOpened) {
		getStreamNative(_stream)->close();
	}
	flagTrueForWriteFalseForRead = trueForWriteFalseForRead;
	if (trueForWriteFalseForRead) {
		// must open in in|out mode. a pure out mode would force trucate or append
		getStreamNative(_stream)->open(filename, std::ios::in | std::ios::out | std::ios::binary | std::ios::ate);
		//_stream = std::fstream(filename, std::ios::out | std::ios::binary);
	}
	else {
		getStreamNative(_stream)->open(filename, std::ios::in | std::ios::binary | std::ios::ate);
		//_stream = std::fstream(filename, std::ios::in | std::ios::binary);
	}
	if (!getStreamNative(_stream)->is_open()) {
		TLogger::writeLine("File [" + filename + "] not exist!");
		ramainingLen = 0;
		return;
	}
	if (trueForWriteFalseForRead) {
		//int curPos = getStreamNative(_stream)->tellp();
		//getStreamNative(_stream)->seekp(0, ios::end);
		ramainingLen = (int)(getStreamNative(_stream)->tellp());
		getStreamNative(_stream)->seekp(0, ios::beg);
	}
	else {
		//int curPos = getStreamNative(_stream)->tellg();
		//getStreamNative(_stream)->seekg(0, ios::end);
		ramainingLen = (int)(getStreamNative(_stream)->tellg());
		getStreamNative(_stream)->seekg(0, ios::beg);
	}
	flagStreamOpened = true;
#endif
}

void TFileStreamer::write(vector<uint8_t>& array) {
	getStreamNative(_stream)->write((char*)(&array[0]), array.size());
}

void TFileStreamer::write(const void* dataPtr, int length) {
	getStreamNative(_stream)->write((char*)dataPtr, length);
	std::fstream& str = *(std::fstream*)_stream;
	str.failbit;
	str.badbit;
}

void TFileStreamer::write(int data) {
	getStreamNative(_stream)->write((char*)&data, 4);
}

void TFileStreamer::write(vector<int>& data) {
	getStreamNative(_stream)->write((char*)(&data[0]), data.size() * 4);
}

int TFileStreamer::getLength() {
	if (flagTrueForWriteFalseForRead) {
		return -1;
	}
	else {
		return ramainingLen;
	}
}

void TFileStreamer::read(int lengthIn, vector<uint8_t>& arrayOut) {
	arrayOut.resize(lengthIn);
	getStreamNative(_stream)->read((char*)(&arrayOut[0]), lengthIn);
	ramainingLen -= lengthIn;
}

void TFileStreamer::read(int lengthIn, void* arrayAllocatedOut) {
	getStreamNative(_stream)->read((char*)arrayAllocatedOut, lengthIn);
	ramainingLen -= lengthIn;
}

void TFileStreamer::gotoPos(size_t goToOffset) {
	if (flagTrueForWriteFalseForRead) {
		getStreamNative(_stream)->seekp(goToOffset, ios::beg);
	}
	else {
		getStreamNative(_stream)->seekg(goToOffset, ios::beg);
	}
}

void TFileStreamer::flush() {
	getStreamNative(_stream)->flush();
}

void TFileStreamer::closeFile() {
	if (flagStreamOpened) {
		getStreamNative(_stream)->close();
	}
	flagStreamOpened = false;
}

void TReadFromBin::readByteStream(std::wstring filename, std::vector<uint8_t>& byteStream) {
	ifstream inBin(filename, ios::binary);
	inBin.seekg(0, ios::end);
	int streamLen = (int)(inBin.tellg());
	if (streamLen > 0) {
		byteStream.resize(streamLen);
		inBin.seekg(0, ios::beg);
		inBin.read((char*)&byteStream[0], streamLen);
		inBin.close();
	}
	else {
#ifdef _CONSOLE
		wcout << L"File " << filename << L" not exist" << endl;
		getchar();
#endif
	}
}

void TReadFromBin::readByteStream(std::string filename, uint8_t*& byteStream, int& streamLen) {
	ifstream inBin(filename, ios::binary);
	inBin.seekg(0, ios::end);
	streamLen = (int)(inBin.tellg());
	if (streamLen == 0) {
		streamLen = 1;
		byteStream = new uint8_t[1];
		return;
	}
	byteStream = new uint8_t[streamLen];
	inBin.seekg(0, ios::beg);
	inBin.read((char*)byteStream, streamLen);
	inBin.close();
}

void TReadFromBin::readByteStream(std::string filename, vector<uint8_t>& byteStream) {
	readByteStream(TFile_string2LPCWSTR(filename), byteStream);
}

std::vector<uint8_t> TReadFromBin::readByteStream(std::string filename) {
	return readByteStream(TFile_string2LPCWSTR(filename));
}

std::vector<uint8_t> TReadFromBin::readByteStream(std::wstring filename) {
	std::vector<uint8_t> stream;
	readByteStream(filename, stream);
	return stream;
}

std::vector<uint8_t> TReadFromBin::readByteStream(std::string filename, size_t offset, size_t count) {
	vector<uint8_t> ret(count);

	ifstream inBin(filename, ios::binary);
	inBin.seekg(offset, ios::beg);
	int pos0 = inBin.tellg();
	inBin.read((char*)&ret[0], count);
	int pos1 = inBin.tellg();
	inBin.close();
	return ret;
}

void TReadFromBin::readByteStream(std::wstring filename, uint8_t** ptr, int* sizeOut) {
	ifstream inBin(filename, ios::binary);
	inBin.seekg(0, ios::end);
	int streamLen = (int)(inBin.tellg());
	if (streamLen > 0) {
		*ptr = new uint8_t[streamLen];
		inBin.seekg(0, ios::beg);
		inBin.read((char*)(*ptr), streamLen);
		inBin.close();
	}
	else {
#ifdef _CONSOLE
		wcout << L"File " << filename << L" not exist" << endl;
		getchar();
#endif
	}
}

TWrite2Txt::TWrite2Txt(std::string filenameIn) {
	setFile(filenameIn);
}

TWrite2Txt::TWrite2Txt(std::wstring filenameIn, bool flagEnable) {
	filenameTXT = filenameIn;
	flagFilenameSet = flagEnable;
}

void TWrite2Txt::clearFile(std::wstring filenameIn) {
	ofstream myfile;
	myfile.open(filenameIn, ios::out);
	myfile.close();
}

void TWrite2Txt::setFile(std::string filenameIn) {
	/*
	VC++17 not support codecvt_utf8_utf16
	Should define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING in macro
	*/
	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
	filenameTXT = converter.from_bytes(filenameIn);
	//filenameTXT = wstring2 std::string(filenameIn);
	if (filenameTXT.size() <= 1)
		return;
	flagFilenameSet = true;
}

void TWrite2Txt::setGlobalFile(std::string filenameIn) {
	filenameGlobalTXT = std::string(filenameIn);
	if (filenameGlobalTXT.size() <= 1)
		return;
	flagFilenameGlobalSet = true;
}

void TWrite2Txt::setFile(char* filenameIn) {
	if (filenameIn == nullptr)
		return;
	setFile(std::string(filenameIn));
}

void TWrite2Txt::setGlobalFile(char* filenameIn) {
	if (filenameIn == nullptr)
		return;
	setGlobalFile(std::string(filenameIn));
}

void TWrite2Txt::writeString(std::string newContent) {
	ofstream myfile;
#ifdef BLOCK_WRITING_LOG

#else
	if (flagFilenameSet) {
		myfile.open(filenameTXT, ios::out | ios::app);
		myfile.write(&(newContent[0]), newContent.size());
		myfile.close();
	}
	else {
		cout << newContent;
	}
#endif
}

void TWrite2Txt::writeString(std::wstring newContent) {
	wofstream myfile;
#ifdef BLOCK_WRITING_LOG

#else
	if (flagFilenameSet) {
		myfile.open(filenameTXT, ios::out | ios::app);
		myfile.write(&(newContent[0]), newContent.size());
		myfile.close();
	}
	else {
		wcout << newContent;
	}
#endif
}

void TWrite2Txt::writeGlobalString(std::string newContent) {
	ofstream myfile;
	if (flagFilenameGlobalSet) {
		myfile.open(filenameGlobalTXT, ios::out | ios::app);
		myfile.write(&(newContent[0]), newContent.size());
		myfile.close();
	}
	else {
		cout << newContent;
	}
}

void TWrite2Txt::writeString(std::string fullContent, std::string filename, bool flagErase, bool flagWithTime) {
#ifdef BLOCK_WRITING_LOG

#else
	if (flagWithTime) {
		int timeMS = TStopWatch::getTimeSinceStart();

		std::ostringstream oss;
		oss << std::setfill('0') << std::setw(9) << timeMS;
		string header = oss.str() + " ";

		fullContent = header + fullContent;
	}
	if (flagErase) {
		ofstream myfile(filename, ios::binary);
		myfile.write(&(fullContent[0]), fullContent.size());
		myfile.close();
	}
	else {
		ofstream myfile(filename, ios::binary | ios::app);
		myfile.write(&(fullContent[0]), fullContent.size());
		myfile.close();
	}
#endif
}

void TWrite2Txt::writeString(std::string fullContent, std::wstring filename, bool flagErase, bool flagWithTime) {
#ifdef BLOCK_WRITING_LOG

#else
	if (flagWithTime) {
		int timeMS = TStopWatch::getTimeSinceStart();

		std::stringstream oss;
		oss << std::setfill('0') << std::setw(9) << timeMS;
		string header = oss.str() + " ";

		fullContent = header + fullContent;
	}
	if (flagErase) {
		ofstream myfile(filename, ios::binary);
		myfile.write(&(fullContent[0]), fullContent.size());
		myfile.close();
	}
	else {
		ofstream myfile(filename, ios::binary | ios::app);
		myfile.write(&(fullContent[0]), fullContent.size());
		myfile.close();
	}
#endif
}

void TWrite2Txt::writeString(std::wstring fullContent, std::wstring filename, bool flagErase, bool flagWithTime) {
#ifdef BLOCK_WRITING_LOG

#else
	if (flagWithTime) {
		int timeMS = TStopWatch::getTimeSinceStart();

		std::wostringstream oss;
		oss << std::setfill(L'0') << std::setw(9) << timeMS;
		wstring header = oss.str() + L" ";

		fullContent = header + fullContent;
	}
	if (flagErase) {
		wofstream myfile(filename, ios::binary);
		myfile.write(&(fullContent[0]), fullContent.size());
		myfile.close();
	}
	else {
		wofstream myfile(filename, ios::binary | ios::app);
		myfile.write(&(fullContent[0]), fullContent.size());
		myfile.close();
	}
#endif
}

void TWrite2Txt::writeLine(std::string newContent) {
	writeString(newContent + "\n");
}

void TWrite2Txt::writeLine(std::wstring newContent) {
	writeString(newContent + L"\n");
}

void TWrite2Txt::writeGlobalLine(std::string newContent) {
	writeGlobalString(newContent + "\n");
}

void TWrite2Txt::writeLine(std::string fullContent, std::string filename, bool flagErase, bool flagWithTime) {
	writeString(fullContent + "\n", filename, flagErase, flagWithTime);
}

string TReadFromTxt::readAllText(std::wstring filename) {
	if (TFile::isFileOrDir(filename) != 0) {
		return "";
	}
	std::ifstream in_file;
	in_file.open(filename);
	std::ostringstream sstr;
	sstr << in_file.rdbuf();
	return sstr.str();
}

wstring TReadFromTxt::readAllWText(std::wstring filename) {
	if (TFile::isFileOrDir(filename) != 0) {
		return L"";
	}
	std::wifstream in_file;
	in_file.open(filename);
	std::wostringstream sstr;
	sstr << in_file.rdbuf();
	return sstr.str();
}

vector<string> TReadFromTxt::readAllLines(wstring filename) {
	ifstream myfile(filename);
	vector<string> allLines;
	string newLine;
	while (getline(myfile, newLine)) {
		allLines.push_back(newLine);
	}
	return allLines;
}

std::vector<std::wstring> TReadFromTxt::readAllLinesW(std::wstring filename) {
	wifstream myfile(filename);
	vector<wstring> allLines;
	wstring newLine;
	while (getline(myfile, newLine)) {
		allLines.push_back(newLine);
	}
	return allLines;
}

std::string TReadFromTxt::findSubstrBetween(std::string baseStr, std::string before, std::string after) {
	int start = baseStr.find(before) + before.length();
	int len = baseStr.find(after, start) - start;
	return baseStr.substr(start, len);
}

char TReadFromTxt::findTextAfter(std::string baseStr, std::string before) {
	int start = baseStr.find(before) + before.length();
	std::string textAfterStr = baseStr.substr(start, 1);
	return textAfterStr[0];
}

int TReadFromTxt::compareTwoTextFiles(std::wstring filename0, std::wstring filename1) {
	vector<wstring> file0 = readAllLinesW(filename0);
	vector<wstring> file1 = readAllLinesW(filename1);
	int size = max(file0.size(), file1.size());
	for (int idx = 0; idx < size; idx++) {
		if (file0[idx].compare(file1[idx]) != 0) {
			return idx;
		}
	}
	if (file0.size() != file1.size()) {
		return size;
	}

	return -1;
}
